package com.sunlee.admin.api.handler;

import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson.JSON;
import com.sunlee.common.service.entity.SysLogErrorEntity;
import com.sunlee.common.service.service.SysLogErrorService;
import com.sunlee.common.core.entity.Result;
import com.sunlee.common.core.enums.ErrorCodeEnum;
import com.sunlee.common.core.exception.BusinessException;
import com.sunlee.common.core.exception.ExcelException;
import com.sunlee.common.core.exception.SysException;
import com.sunlee.common.core.exception.util.ExceptionUtils;
import com.sunlee.common.core.utils.HttpContextUtils;
import com.sunlee.common.core.utils.IpUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpHeaders;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


/**
 * 全局异常捕获处理器
 *
 * @author sunlee
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @Resource
    private SysLogErrorService sysLogErrorService;

    /**
     * 处理通用自定义业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public Result<Object> handleBusinessException(
            BusinessException e, HttpServletRequest request) {
        logger.warn("handleBusinessException start, code:【{}】, msg:【{}】, uri:【{}】, exception:【{}】, caused by: 【{}】"
                , e.getCode()
                , e.getMsg()
                , request.getRequestURI()
                , e.getClass().getSimpleName()
                , e.getMessage()
                , e
        );
        return Result.error(e);
    }

    /**
     * 系统异常
     */
    @ExceptionHandler(SysException.class)
    public Result<Object> handleSysException(
            SysException e, HttpServletRequest request) {
        logger.error("handleSysException start, code:【{}】, msg:【{}】, uri:【{}】, exception:【{}】, caused by: 【{}】"
                , e.getCode()
                , e.getMsg()
                , request.getRequestURI()
                , e.getClass().getSimpleName()
                , e.getMessage()
                , e
        );
        return Result.error(e.getCode(), e.getMsg());
    }

    /**
     * Excel相关操作异常
     */
    @ExceptionHandler(ExcelException.class)
    public Result<Object> handleExcelException(ExcelException e, HttpServletRequest request) {
        logger.error("handleExcelException start, code:【{}】, msg:【{}】, uri:【{}】, exception:【{}】, caused by: 【{}】"
                , e.getCode()
                , e.getMsg()
                , request.getRequestURI()
                , e.getClass().getSimpleName()
                , e.getMessage()
                , e
        );
        saveLog(e);
        return Result.error(e.getErrorCodeEnum());
    }

    /**
     * 主键重复异常
     */
    @ExceptionHandler(DuplicateKeyException.class)
    public Result<Object> handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) {
        logger.warn("handleDuplicateKeyException start, uri:【{}】, exception:【{}】, caused by: 【{}】"
                , request.getRequestURI()
                , e.getClass().getSimpleName()
                , (e.getCause() == null ? e.getMessage() : e.getCause().getMessage())
                , e
        );
        return Result.error(ErrorCodeEnum.DATA_ALREADY_EXISTED_NO_PARAM.getMsg());
    }

    /**
     * 主键重复异常
     */
    @ExceptionHandler(DataIntegrityViolationException.class)
    public Result<Object> handleDataIntegrityViolationException(DataIntegrityViolationException e, HttpServletRequest request) {
        return Result.error(ErrorCodeEnum.EXIST_DATA_DELETE_ERROR.getMsg());
    }

    /**
     * 违反参数约束异常处理
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public Result<Object> handleConstraintViolationException(
            ConstraintViolationException e, HttpServletRequest request) {
        logger.warn("handleConstraintViolationException start, uri:【{}】, exception:【{}】, caused by: 【{}】"
                , request.getRequestURI()
                , e.getClass().getSimpleName()
                , (e.getCause() == null ? e.getMessage() : e.getCause().getMessage())
                , e
        );
        String detailMessage = e.getConstraintViolations()
                .stream()
                .map(violation -> violation.getPropertyPath().toString() + ":" + violation.getMessage())
                .collect(Collectors.joining(","));
        if (StringUtils.isEmpty(detailMessage)) {
            detailMessage = e.getMessage();
        }

        return Result.error(ErrorCodeEnum.PARAM_IS_INVALID.format(detailMessage));
    }

    /**
     * Web接口入参类型异常处理
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public Result<Object> handleMissingServletRequestParameterException(
            MissingServletRequestParameterException e, HttpServletRequest request) {

        logger.warn("handleMissingServletRequestParameterException, uri:【{}】, exception:【{}】, caused by: 【{}】"
                , request.getRequestURI()
                , e.getClass().getSimpleName()
                , (e.getCause() == null ? e.getMessage() : e.getCause().getMessage())
                , e
        );

        return Result.error(ErrorCodeEnum.PARAM_IS_BLANK.format(e.getParameterName()));
    }

    /**
     * Web接口入参类型异常处理
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public Result<Object> handleMethodArgumentTypeMismatchException(
            MethodArgumentTypeMismatchException e, HttpServletRequest request) {
        logger.warn("handleMethodArgumentTypeMismatchException, uri:【{}】, exception:【{}】, caused by: 【{}】"
                , request.getRequestURI()
                , e.getClass().getSimpleName()
                , (e.getCause() == null ? e.getMessage() : e.getCause().getMessage())
                , e
        );

        return Result.error(ErrorCodeEnum.PARAM_TYPE_BIND_ERROR.format(e.getName()));
    }

    /**
     * Web接口入参类型异常处理
     */
    @ExceptionHandler(IllegalArgumentException.class)
    public Result<Object> handleIllegalArgumentException(
            IllegalArgumentException e, HttpServletRequest request) {
        logger.warn("handleIllegalArgumentException, uri:【{}】, exception:【{}】, caused by: 【{}】"
                , request.getRequestURI()
                , e.getClass().getSimpleName()
                , (e.getCause() == null ? e.getMessage() : e.getCause().getMessage())
                , e
        );

        return Result.error(ErrorCodeEnum.PARAM_IS_INVALID.format(e.getMessage()));
    }

    /**
     * 其他类型异常
     */
    @ExceptionHandler(Exception.class)
    public Result<Object> handleException(Exception e, HttpServletRequest request) {
        logger.error("handleException start, uri:【{}】, caused by: ", request.getRequestURI(), e);
        saveLog(e);
        return Result.error();
    }

    /**
     * 数据校验异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {

        BindingResult result = e.getBindingResult();
        String errMsg = "";
        if (result.hasErrors()) {
            List<String> errors;
            List<ObjectError> allErrors = result.getAllErrors();
            errors = new ArrayList<>();
            for (ObjectError error : allErrors) {
                errors.add(error.getDefaultMessage());
            }
            errMsg = errors.toString().replace("[","").replace("]","");

        }

        ErrorCodeEnum errEnum = ErrorCodeEnum.PARAMETER_ERROR.format(errMsg);
        logger.warn("handleMethodArgumentNotValidException start, code:【{}】, msg:【{}】, uri:【{}】"
                , errEnum.getCode()
                , errEnum.getMsg()
                , request.getRequestURI()
        );

        return Result.error(errEnum);
    }

    /**
     * 保存异常日志
     */
    private void saveLog(Exception ex) {
        SysLogErrorEntity log = new SysLogErrorEntity();

        //请求相关信息
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();

        log.setIp(IpUtils.getIpAddr(request));

        if (!ObjectUtils.isEmpty(request)) {
            log.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
            log.setRequestUri(request.getRequestURI());
            log.setRequestMethod(request.getMethod());
            Map<String, String> params = HttpContextUtils.getParameterMap(request);
            if (MapUtil.isNotEmpty(params)) {
                log.setRequestParams(JSON.toJSONString(params));
            }
        }

        //异常信息
        log.setErrorInfo(ExceptionUtils.getErrorStackTrace(ex));

        //保存
        sysLogErrorService.saveOwn(log);
    }
}